package top.mingyi4cjh.cms.service.impl;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.mingyi4cjh.cms.dao.UserInfoDOMapper;
import top.mingyi4cjh.cms.dao.UserLoginDOMapper;
import top.mingyi4cjh.cms.dao.UserPasswordDOMapper;
import top.mingyi4cjh.cms.dataobject.UserInfoDO;
import top.mingyi4cjh.cms.dataobject.UserLoginDO;
import top.mingyi4cjh.cms.dataobject.UserPasswordDO;
import top.mingyi4cjh.cms.common.error.BusinessException;
import top.mingyi4cjh.cms.common.error.EmBusinessError;
import top.mingyi4cjh.cms.model.UserLoginModel;
import top.mingyi4cjh.cms.model.UserModel;
import top.mingyi4cjh.cms.service.RedisService;
import top.mingyi4cjh.cms.service.UserService;
import top.mingyi4cjh.cms.common.utils.RedisPrefix;
import top.mingyi4cjh.cms.common.validator.ValidationResult;
import top.mingyi4cjh.cms.common.validator.ValidatorImpl;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import static top.mingyi4cjh.cms.common.utils.Field.QUARTER;
import static top.mingyi4cjh.cms.common.utils.UuidManager.UUID_MANAGER;

/**
 * @author MingYi
 * @program cms
 * @create 2022/04/21 23:35
 */
@Service
public class UserServiceImpl implements UserService {
    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private UserInfoDOMapper userInfoMapper;

    @Resource
    private UserLoginDOMapper userLoginDOMapper;

    @Resource
    private UserPasswordDOMapper userPasswordMapper;

    @Resource
    private RedisService redisService;

    @Resource
    private ValidatorImpl validator;

    @Override
    public UserModel validateLogin(String userName, String password) throws BusinessException, NoSuchAlgorithmException {
        UserInfoDO userInfoDO = userInfoMapper.selectByUserName(userName);
        if (userInfoDO == null) {
            BusinessException e = new BusinessException(EmBusinessError.USER_LOGIN_FAIL);
            logger.info("User [{}] not found.", userName);
            throw e;
        }
        UserPasswordDO userPasswordDO = userPasswordMapper.selectByPrimaryKey(userInfoDO.getUserId());
        if (userPasswordDO == null) {
            logger.warn("Getting user {} password error.", userInfoDO.getUserId());
            throw new BusinessException(EmBusinessError.GETTING_INFORMATION_ERROR);
        }
        UserModel userModel = convertFromDataObject(userInfoDO, userPasswordDO);

        // 取出六位的salt拼接给password，再对其加密，对比
        String salt = userModel.getSalt();
        String encryptPassword = encode(password + salt);

        if (!StringUtils.equals(encryptPassword, userModel.getPassword())) {
            BusinessException e = new BusinessException(EmBusinessError.USER_LOGIN_FAIL);
            logger.info("User [{}] login fail, error message: {}", userName, e.getErrMsg());
            throw e;
        }
        return userModel;
    }

    @Override
    public void recordLogin(UserLoginModel userLoginModel) throws BusinessException {
        ValidationResult result = validator.validate(userLoginModel);
        if (result.isHasErrors()) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, result.getErrMsg());
        }

        UserLoginDO userLoginDO = convertFromModel(userLoginModel);
        userLoginDOMapper.insertSelective(userLoginDO);
    }

    @Override
    public UserModel getUserById(Long userId) {
        // 入参校验
        if (userId == null) {
            return null;
        }
        UserInfoDO userInfoDO = userInfoMapper.selectByPrimaryKey(userId);
        // 没有该用户
        if (userInfoDO == null) {
            return null;
        }
        UserLoginDO userLoginDO = userLoginDOMapper.selectByUserId(userId);

        return convertFromUserInfo(userInfoDO, userLoginDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void registerByEmail(UserModel userModel, String otpCode) throws BusinessException, NoSuchAlgorithmException {
        // 判空
        if (userModel == null || StringUtils.isEmpty(otpCode)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }
        // 检查otpCode是否和email匹配
        if (!otpCode.equals(redisService.get(RedisPrefix.MAIL_OTP.getPrefix() + userModel.getEmail()))) {
            BusinessException e = new BusinessException(EmBusinessError.OTP_CODE_ERROR);
            logger.info("User [{}] used wrong otpCode.", userModel.getUserName());
            throw e;
        }
        // 检查数据是否合规
        ValidationResult result = validator.validate(userModel);
        if (result.isHasErrors()) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, result.getErrMsg());
        }
        // 检查用户是否已经存在
        if (userInfoMapper.selectByUserName(userModel.getUserName()) != null) {
            throw new BusinessException(EmBusinessError.USER_ALREADY_EXIST);
        }
        // 检查邮箱是否已被使用过
        if (userInfoMapper.selectByEmail(userModel.getEmail()) != null) {
            throw new BusinessException(EmBusinessError.EMAIL_ALREADY_USED);
        }

        // 生成userId
        userModel.setUserId(UUID_MANAGER.getUuid());
        // 转换并存储
        UserInfoDO userInfoDO = convertFromModel(userModel);
        userInfoMapper.insertSelective(userInfoDO);
        UserPasswordDO userPasswordDO = convertPasswordFromModel(userModel);
        userPasswordMapper.insertSelective(userPasswordDO);
    }

    @Override
    public void registerByPhone(UserModel userModel, String otpCode) throws BusinessException, NoSuchAlgorithmException {

    }

    @Override
    public boolean checkEmail(String userName, String email) throws BusinessException {
        if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(email)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }
        //先从redis取得信息，取不到则从mysql中取
        String infoEmail;
        infoEmail = (String) redisService.get(RedisPrefix.MAIL_NAME.getPrefix() + userName);
        if (StringUtils.isEmpty(infoEmail)) {
            UserInfoDO userInfoDO = userInfoMapper.selectByUserName(userName);
            if (userInfoDO == null) {
                logger.warn("UserName {} and email {} is mismatch.", userName, email);
                throw new BusinessException(EmBusinessError.GETTING_INFORMATION_ERROR, "身份信息有误");
            }
            infoEmail = userInfoDO.getEmail();
        }

        // redis中存一份对应值,ttl = 15min,为保证获取信息方便，反向也存一份
        redisService.set(RedisPrefix.MAIL_NAME.getPrefix() + infoEmail, userName, QUARTER);
        redisService.set(RedisPrefix.NAME_MAIL.getPrefix() + userName, infoEmail, QUARTER);

        return email.equals(infoEmail);
    }

    @Override
    public UserModel checkIdentify(String email, String otpCode) throws BusinessException {
        // 入参校验
        if (StringUtils.isEmpty(email) || StringUtils.isEmpty(otpCode)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }
        // 验证Otp
        String userName = (String) redisService.get(RedisPrefix.MAIL_NAME.getPrefix() + email);
        if (!otpCode.equals(redisService.get(RedisPrefix.MAIL_OTP.getPrefix() + email))) {
            BusinessException e = new BusinessException(EmBusinessError.OTP_CODE_ERROR);
            logger.info("User [{}] used wrong otpCode.", userName);
            throw e;
        }
        // 读取数据库，获取userId
        UserInfoDO userInfoDO = userInfoMapper.selectByEmail(email);
        Long userId = userInfoDO.getUserId();
        // 存入redis
        redisService.set(RedisPrefix.RESET_PASSWORD.getPrefix() + userId, userName, QUARTER);
        return convertFromUserInfo(userInfoDO);
    }

    @Override
    public void resetPassword(Long userId, String password) throws BusinessException, NoSuchAlgorithmException {
        //入参校验
        if (userId == null || StringUtils.isEmpty(password)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }
        //资格校验
        if (!redisService.hasKey(RedisPrefix.RESET_PASSWORD.getPrefix() + userId)) {
            BusinessException e = new BusinessException(EmBusinessError.AUTHENTICATION_NOT_VERIFIED);
            logger.warn("Not verified reset password of userId [{}]", userId);
            throw e;
        }

        //生成盐和密文
        String salt = RandomStringUtils.randomAlphanumeric(6);
        String newPassword = password + salt;
        String encryptPassword = encode(newPassword);

        //存入UserPasswordDO instance
        UserPasswordDO userPasswordDO = new UserPasswordDO();
        userPasswordDO.setUserId(userId);
        userPasswordDO.setPassword(encryptPassword);
        userPasswordDO.setSalt(salt);
        //写入数据库
        userPasswordMapper.updateByPrimaryKey(userPasswordDO);
    }

    @Override
    public void cleanCacheOfReset(Long id) {
        if (!redisService.hasKey(RedisPrefix.RESET_PASSWORD.getPrefix() + id)) {
            return;
        }
        String userName = (String) redisService.get(RedisPrefix.RESET_PASSWORD.getPrefix() + id);
        String email = (String) redisService.get(RedisPrefix.NAME_MAIL.getPrefix() + userName);

        redisService.del(RedisPrefix.MAIL_NAME.getPrefix() + email);
        redisService.del(RedisPrefix.RESET_PASSWORD.getPrefix() + id);
        redisService.del(RedisPrefix.NAME_MAIL.getPrefix() + userName);
        redisService.del(RedisPrefix.MAIL_TTL.getPrefix() + email);
        redisService.del(RedisPrefix.MAIL_OTP.getPrefix() + email);

        redisService.del(RedisPrefix.ID_NAME.getPrefix() + id);
        redisService.del(RedisPrefix.ID_MAIL.getPrefix() + id);
    }

    @Override
    public void saveIdOtp(UserModel userModel, String otpCode) throws BusinessException {
        if (userModel == null || StringUtils.isEmpty(otpCode)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }
        // 保存id-mail, email-otpCode, id-userName, userName-mail ttl均为15min
        redisService.set(RedisPrefix.ID_MAIL.getPrefix() + userModel.getUserId(), userModel.getEmail(), QUARTER);
        redisService.set(RedisPrefix.MAIL_OTP.getPrefix() + userModel.getEmail(), otpCode, QUARTER);
        redisService.set(RedisPrefix.ID_NAME.getPrefix() + userModel.getUserId(), userModel.getUserName(), QUARTER);
        redisService.set(RedisPrefix.NAME_MAIL.getPrefix() + userModel.getUserName(), userModel.getEmail(), QUARTER);
    }

    @Override
    public void checkStatus(Long id, String otpCode) throws BusinessException {
        if (id == null || StringUtils.isEmpty(otpCode)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
        }
        // 检查id-otpCode的对应关系
        String email = (String) redisService.get(RedisPrefix.ID_MAIL.getPrefix() + id);
        if (!otpCode.equals(redisService.get(RedisPrefix.MAIL_OTP.getPrefix() + email))) {
            throw new BusinessException(EmBusinessError.OTP_CODE_ERROR);
        }
        // 检查通过则存入重置权限
        String userName = (String) redisService.get(RedisPrefix.ID_NAME.getPrefix() + id);
        redisService.set(RedisPrefix.RESET_PASSWORD.getPrefix() + id, userName, QUARTER);
    }

    /**
     * 根据userModel中的属性，截取部分置于userInfoDO中并返回
     *
     * @param userModel UserModel instance
     * @return UserInfo instance
     */
    private UserInfoDO convertFromModel(UserModel userModel) {
        if (userModel == null) {
            return null;
        }
        UserInfoDO userInfoDO = new UserInfoDO();
        BeanUtils.copyProperties(userModel, userInfoDO);
        return userInfoDO;
    }

    private UserLoginDO convertFromModel(UserLoginModel userLoginModel) {
        if (userLoginModel == null) {
            return null;
        }

        UserLoginDO userLoginDO = new UserLoginDO();
        BeanUtils.copyProperties(userLoginModel, userLoginDO);
        return userLoginDO;
    }

    /**
     * 根据userModel中的属性，截取部分置于userPasswordDO中并返回
     *
     * @param userModel UserModel instance
     * @return UserPasswordDO instance
     */
    private UserPasswordDO convertPasswordFromModel(UserModel userModel) throws NoSuchAlgorithmException {
        if (userModel == null) {
            return null;
        }
        UserPasswordDO userPasswordDO = new UserPasswordDO();

        // 计算盐和密文
        String salt = RandomStringUtils.randomAlphanumeric(6);
        String newPassword = userModel.getPassword() + salt;
        String encryptPassword = encode(newPassword);

        // 存入密文和盐
        userPasswordDO.setUserId(userModel.getUserId());
        userPasswordDO.setPassword(encryptPassword);
        userPasswordDO.setSalt(salt);

        return userPasswordDO;
    }

    /**
     * 根据userInfoDO和userPasswordDO的属性，合并成一个userModel并返回
     *
     * @param userInfoDO     UserInfoDO instance
     * @param userPasswordDO UserPasswordDO instance
     * @return UserModel instance
     */
    private UserModel convertFromDataObject(UserInfoDO userInfoDO, UserPasswordDO userPasswordDO) {
        if (userInfoDO == null) {
            return null;
        }
        UserModel userModel = new UserModel();
        BeanUtils.copyProperties(userInfoDO, userModel);
        if (userPasswordDO != null) {
            userModel.setPassword(userPasswordDO.getPassword());
            userModel.setSalt(userPasswordDO.getSalt());
        }
        return userModel;
    }

    /**
     * 将UserInfoDO的数据存入到UserModel中。
     *
     * @param userInfoDO UserInfoDO instance
     * @return UserModel instance
     */
    private UserModel convertFromUserInfo(UserInfoDO userInfoDO) {
        if (userInfoDO == null) {
            return null;
        }
        UserModel userModel = new UserModel();
        BeanUtils.copyProperties(userInfoDO, userModel);
        return userModel;
    }

    private UserModel convertFromUserInfo(UserInfoDO userInfoDO, UserLoginDO userLoginDO) {
        if (userInfoDO == null) {
            return null;
        }
        UserModel userModel = new UserModel();
        BeanUtils.copyProperties(userInfoDO, userModel);
        userModel.setIp(userLoginDO.getIp());
        userModel.setLoginDate(userLoginDO.getLoginDate());
        return userModel;
    }

    /**
     * 使用SHA-256对字符串进行加密并返回加密后的结果
     *
     * @param str 待加密的string
     * @return Encrypted ciphertext using SHA-256
     */
    public String encode(String str) throws NoSuchAlgorithmException {
        MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
        sha256.update(str.getBytes(StandardCharsets.UTF_8));
        byte[] bytes = sha256.digest();
        return Base64.encodeBase64String(bytes);
    }
}

